/**
 * 
 */
package simulation;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import org.apache.hadoop.io.WritableComparable;

/**
 * @author aclayton
 *
 */
public class ConfigGraph implements WritableComparable{
	private HashMap<Configuration, ArrayList<Configuration>> ajacencyList;
	private Rect boundingBox;
	
	public ConfigGraph(){
		ajacencyList = new	HashMap<Configuration, ArrayList<Configuration>>();
		boundingBox = null;
	}
	
	public ConfigGraph(Rect boundingBox){
		ajacencyList = new	HashMap<Configuration, ArrayList<Configuration>>();
		this.boundingBox = boundingBox;
	}
	
	public void addGraph(ConfigGraph config){
		ajacencyList.putAll(config.ajacencyList);
		boundingBox.adjacentRect(config.boundingBox);
	}
	
	public Configuration closestConfig(Configuration config){
		Configuration closest = null;
		double dis = -1, closestdis = -1;;
		for(Configuration c : ajacencyList.keySet()){
			dis = config.getDistanceFromConfig(c);
			if(closest == null || dis < closestdis){
				closest = c;
				closestdis = dis;
			}
		}
		return closest;
	}
	
	public void addConfiguration(Configuration config){
		if(!ajacencyList.containsKey(config))
			ajacencyList.put(config, new ArrayList<Configuration>());
	}
	
	public void addEdge(Configuration config1, Configuration config2){
		ArrayList<Configuration> temp = ajacencyList.get(config1);
		temp.add(config2);
		
		temp = ajacencyList.get(config2);
		temp.add(config1);
	}
	
	public ArrayList<Configuration> getConnected(Configuration config){
		return ajacencyList.get(config);
	}
	
	public Rect getActualBoundingBox(){
		Rect box = null;
		for(Configuration c : ajacencyList.keySet()){
			if(box == null)
				box = c.getBoundingBox();
			else
				box = box.union(c.getBoundingBox());
		}
		return box;
	}
	
	public Rect getBoundingBox(){
		return boundingBox;
	}
	
	public Configuration getRandomConfiguration(){
		Random r = new Random();
		Configuration[] configs = new Configuration[ajacencyList.keySet().size()];
		configs = ajacencyList.keySet().toArray(configs);
		int num = Math.abs(r.nextInt())%configs.length;
		return configs[num];
	}
	
	public HashSet<Configuration> getRandomConfigurationSet(int numConfigs){
		if(numConfigs >= ajacencyList.size())
			numConfigs = ajacencyList.size();
		
		HashSet<Configuration> randomset = new HashSet<Configuration>();
		
		while(randomset.size() < numConfigs){
			randomset.add(getRandomConfiguration());
		}
		
		return randomset;
	}
	
	public Set<Configuration> getConfigurations(){
		return ajacencyList.keySet();
	}
	
	public Configuration getClosestConfiguration(Configuration con){
		Configuration closest = null;
		double distance = -1, tempdis;
		for(Configuration c : ajacencyList.keySet()){
			tempdis = con.getConfigCenter().getDistance(c.getConfigCenter());
			if(distance == -1 || tempdis < distance){
				closest = c;
				distance = tempdis;
			}
		}
		return closest;
	}
	
	public HashSet<Configuration> getNearestConfigsDistance(Configuration config, double dis){
		HashSet<Configuration> nearest = new HashSet<Configuration>();
		
		for(Configuration c : ajacencyList.keySet()){
			if(config.getConfigCenter().getDistance(c.getConfigCenter()) < dis)
				nearest.add(c);
		}
		return nearest;
	}

	@Override
	public void readFields(DataInput arg0) throws IOException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void write(DataOutput arg0) throws IOException {
		// TODO Auto-generated method stub
		
	}

	@Override
	public int compareTo(Object arg0) {
		// TODO Auto-generated method stub
		return 0;
	}
	
	public Path getAStarPath(Configuration start, Configuration end){
		HashSet<Configuration> closedSet = new HashSet<Configuration>();
		HashSet<Configuration> openSet = new HashSet<Configuration>();
		openSet.add(start);
		HashMap<Configuration, Configuration> camefrom = new HashMap<Configuration, Configuration>();
		camefrom.put(start, start);
		
		
		HashMap<Configuration, Double> g_score = new HashMap<Configuration, Double>();
		HashMap<Configuration, Double> h_score = new HashMap<Configuration, Double>();
		HashMap<Configuration, Double> f_score = new HashMap<Configuration, Double>();
		
		g_score.put(start, new Double(0));
		h_score.put(start, start.getDistanceFromConfig(end));
		f_score.put(start, g_score.get(start)+h_score.get(start));
		
		while(!openSet.isEmpty()){
			Configuration lowestF = null;
			for(Configuration c : openSet){
				if(lowestF == null || f_score.get(c)<f_score.get(lowestF))
					lowestF = c;
			}
			
			if(lowestF == end)
				return recontructPath(camefrom, end);
			
			openSet.remove(lowestF);
			closedSet.add(lowestF);
			
			for(Configuration y : ajacencyList.get(lowestF)){
				if(closedSet.contains(y))
					continue;
				
				double tentative_g_score = g_score.get(lowestF) + lowestF.getDistanceFromConfig(y);
				boolean tentative_is_better;
				if(!openSet.contains(y)){
					openSet.add(y);
					tentative_is_better = true;
				} else if(tentative_g_score < g_score.get(y)){
					tentative_is_better = true;
				} else {
					tentative_is_better = false;
				}
				
				if(tentative_is_better){
					camefrom.put(y, lowestF);
					g_score.put(y, tentative_g_score);
					h_score.put(y, y.getDistanceFromConfig(end));
					f_score.put(y, g_score.get(y)+h_score.get(y));
				}
			}
		}
		
		// Run astar to get path
		return null;
	}
	public Path recontructPath(HashMap<Configuration, Configuration> camefrom, Configuration node){
		ArrayList<Configuration> path = new ArrayList<Configuration>();
		Configuration current = node;
		path.add(current);
		
		while(camefrom.get(current) != current){
			path.add(camefrom.get(current));
			current = camefrom.get(current);
		}
		
		Collections.reverse(path);
		
		return new Path(path);
	}
}
